package engine;
import java.util.Iterator;
import java.util.Vector;
import com.jamonapi.*;
import org.apache.log4j.Logger;


/**
 *  class Engine
 *
 *  This class holds static methods for finding the best move
 *  give a certain position.
 *
 *  @author Brandon Ridenour (brandon.ridenour@gmail.com)
 *  Date: 2007-02-17
 */



public class Brain implements Constants
{
	public static final int PLY_DEPTH =5;
	public static Vector pv = new Vector();
	final static String className = Brain.class.getName();
	final static Logger LOG = Logger.getLogger(className);
	public static int depthSearched;
	public static int nodesSearched;
	static private boolean skipNullMove;
	
	/**
	 *  Calls the alpha beta and returns
	 *  the principal variation
	 *
	 *  @param Chessboard The board on which to find the moves
	 *  @return Vector The principal variation
	 */
	public static LineEval search(Chessboard board)
	{
		LOG.debug("Entering Brain::Search");
				
		// we will null move check every other move 
		skipNullMove = false;
		
		Monitor mon=null;	
		mon = MonitorFactory.start("search");
		
		// reset nodes searched
		//
		nodesSearched = 0;
		
		long startTime = System.currentTimeMillis(); 
		
		LOG.info("Number of PLIES evaluated: " + PLY_DEPTH);
		
		//  begin iteratively deepening search starting at ply depth 2, and then incrementing by 1 until time
		//  expires
		int ply = 2;
		
		while (time not expired){
			int eval = alphaBeta(board,ply,-200000,200000, pv);
			ply++;
		}
		
		long stopTime = System.currentTimeMillis(); 
		long runTime = (stopTime - startTime);  // in seconds
		//long nodesPerSec = nodesSearched/runTime;
		
		LOG.error("Nodes searched: " + nodesSearched);
		mon.stop();
		LOG.error("Total Thinking Time: " + runTime);
		LOG.error("Brain Evaluation:  " + (eval));
		LOG.error("FEN:  " + board.getFEN());
	
		String pieces = null;
		
		Iterator i = board.whitePieces.iterator();
		Piece p = null;
		while(i.hasNext()){
			p = (Piece)i.next();
			pieces = pieces +  p.piece_type + " " + p.piece_location + ", ";  
		}
		
		LOG.error("White Array Contents:  " + pieces);
		
		Iterator b = board.blackPieces.iterator();
		
		pieces = null;
		while(b.hasNext()){
			p = (Piece)b.next();
			pieces = pieces + p.piece_type + " " + p.piece_location + ", ";  
		}
		
		Iterator v = pv.iterator();
		String line = "";
		Move mv = null;
		while (v.hasNext()){
			mv = (Move)v.next();
			line += mv.inputNotation() + ", "; 
		}
		LOG.error("Best Line Found: " + line);
		
		LOG.error("Black Array Contents:  " + pieces);
		
		LineEval finalEval = new LineEval(pv, eval);
		LOG.debug("Exiting Brain::Search");
		return finalEval;
	}
	// END search()
	
	/**
	 *  class LineEval
	 *
	 *  Holds a line and evaluation number used to return in search
	 */
	public static class LineEval
	{
		public Vector line;
		public int eval;
		
		public LineEval()
		{
			this.line = new Vector();
			this.eval = 0;
		}
		
		public LineEval(Vector line, int eval)
		{
			this.line = line;
			this.eval = eval;
		}
	}

	/**
	 *  The alpha-beta search.
	 *
	 *  @param Chessboard The position we're at
	 *  @param int The current ply we're at
	 *  @param int The alpha value
	 *  @param int The beta value
	 *  @param Vector Passing the principal variation through the trees
	 *  @return int Depending on where in the search we are
	 *              it returns alpha, beta (cutoff) or the
	 *              final evaluation
	 */
	public static int alphaBeta(Chessboard board, int ply, int alpha, int beta, Vector pv)
	{
		
		// increment nodes searched for monitoring speed
		//
		nodesSearched += 1;
		Monitor mon = null;
		Monitor monMoves = null;		
		int val = 0;
				
		Vector localpv = new Vector(); // Holds this ply's pv
					
		//if (moves.size() == 0) return mateCheck(board, ply);
		if(ply <= 0){
			return quiescenceSearch(board, alpha, beta);
			 //return evaluatePosition(board);
		}
		
		if (skipNullMove == false && board.gamePhase !=END_GAME){
			// change whose turn it is to move for null move pruning
			 board.toMove = board.toMove * -1;
			 val = -alphaBeta(board, ply - 3, -beta, -beta + 1, pv);
			 board.toMove = board.toMove * -1;
			 if (val >= beta)
			      return beta;
			 skipNullMove = true;
		}else
			skipNullMove=false;
		
		monMoves = MonitorFactory.start("generateMoves");
		Vector moves = board.generateMoves(false); // Generate all legal moves for the position
		monMoves.stop();
		
		Quicksort.Quicksort(board, moves, 0, (moves.size()-1));
		
		depthSearched += 1;
		//LOG.info("Depth searched: " + depthSearched);
		
		
					
		for(int i = 0; i < moves.size(); i++) // Loop through all the moves
		{
			mon = MonitorFactory.start("makeMove");
			//LOG.info("FEN Before Move= " + board.getFEN());
			board.makeMove((Move)moves.get(i)); // Make the move on the board
			mon.stop();
			//LOG.info("MOVE = " + ((Move)moves.get(i)).moveType + ", " + ((Move)moves.get(i)).fromIndex + ", " + ((Move)moves.get(i)).toIndex);
			//LOG.info("FEN= " + board.getFEN()); 
			//LOG.info("PLY= " + ply);
			//LOG.info("Depth Searched: " + depthSearched);
			//LOG.info("MOVE ELAPSED TIME= " + mon.toString());
			
			// Call the method again with the updated board, one less ply and
			// swapped alpha and beta. This will return the evaluation after
			// all recursive calls are done.
			int eval = -alphaBeta(board, ply-1, -beta, -alpha, localpv);
			
			board.unmakeMove((Move)moves.get(i)); // Reset the board
			
			//LOG.debug("after unmake - FEN= " + board.getFEN()); 
			
			if(eval >= beta) // If the evalatuion is bigger than beta, cutoff
				return beta;

			if(eval > alpha) // If the evaluation is bigger than alpha (but less than beta) this is our new best move
			{
				// Extract the principal variation
				//
				// If there is nothing in the pv so far, add a new move
				if(pv.size() == 0)
					pv.add(moves.get(i));
				else // Else replace the old move
					pv.set(0, moves.get(i)); // Add the move at first index

				// Append the localpv to the pv
				for(int j = 0; j < localpv.size(); j++)
				{
					// Check if we have a spot ready in the pv or if
					// we need to add a new one.
					if(pv.size() -2 < j)
						pv.add(j+1, localpv.get(j));
					else
						pv.set(j+1, localpv.get(j));
				}

				// Remember the new best value
				alpha = eval;
			}
		}
		depthSearched -= 1;

		// Check for mate/stalemate
		if(moves.size() == 0) return mateCheck(board, ply);
			
		

			
	//	catch(ArrayIndexOutOfBoundsException ex)
		return alpha;
	}

	private static int quiescenceSearch(Chessboard board, int alpha, int beta) {
		
		Monitor mon = null;
		Monitor monMoves = null;
		
		int eval = evaluatePosition(board);
		
		if(eval >= beta) // If the evalatuion is bigger than beta, cutoff
			return beta;

		if(eval > alpha) // If the evaluation is bigger than alpha (but less than beta) this is our new best move
		{
			alpha = eval;
		}
		
		// increment nodes searched for monitoring speed
		//
		nodesSearched += 1;		

		
		monMoves = MonitorFactory.start("generateMoves");
		Vector moves = board.generateMoves(true); // Generate moves for the position
		monMoves.stop();

		//LOG.debug("QS: FEN Before Move= " + board.getFEN());
					
		Quicksort.Quicksort(board, moves, 0, (moves.size()-1));
		
		/*  for debugging sorting
		Iterator it = moves.iterator();
		String moveScores = "";
		while(it.hasNext()){
			moveScores += ((Move)it.next()).moveScore;
			moveScores += " ,";
		}
		LOG.info("Sorted moves:  " + moveScores);
		*/
		
			depthSearched += 1;
			//LOG.debug("Depth searched: " + depthSearched);
			for(int i = 0; i < moves.size(); i++) // Loop through all the moves
			{
				board.makeMove((Move)moves.get(i)); // Make the move on the board
				
				//LOG.info("MOVE = " + ((Move)moves.get(i)).moveType + ", " + ((Move)moves.get(i)).fromIndex + ", " + ((Move)moves.get(i)).toIndex);
				//LOG.info("FEN= " + board.getFEN()); 
				//LOG.info("PLY= " + ply);
				//LOG.info("Depth Searched: " + depthSearched);
				//LOG.info("MOVE ELAPSED TIME= " + mon.toString());
				
				// Call the method again with the updated board, one less ply and
				// swapped alpha and beta. This will return the evaluation after
				// all recursive calls are done.
				eval = -quiescenceSearch(board, -beta, -alpha);
				//LOG.info("move: " + ((Move)moves.get(i)).inputNotation());
				board.unmakeMove((Move)moves.get(i)); // Reset the board
				
				//LOG.debug("after unmake - FEN= " + board.getFEN()); 
				
				if(eval >= beta) // If the evalatuion is bigger than beta, cutoff
					return beta;

				if(eval > alpha) // If the evaluation is bigger than alpha (but less than beta) this is our new best move
				{
					
					// Remember the new best value
					alpha = eval;
				}
			}
			depthSearched = depthSearched -  1;

			
		
		return alpha;
				
	}
	private static boolean positionIsQuiet(Chessboard board) {
		
		
		// first lets see if there are any threats in this position.  If there are, its not quiet
		//
		Vector defenders = null;
		Piece piece = null;
		
		if (board.toMove == WHITE_TO_MOVE)
			defenders = board.blackPieces;
		else
			defenders = board.whitePieces;
		
		Iterator i = defenders.iterator();
		
		while(i.hasNext()){
			piece = (Piece)i.next();
			if(board.isAttacked(piece.piece_location))
				return false;
		}
		
		// no piece is under threat, position is quiet
		//
		return true;
	}
	//END alphaBeta()
	
		
	


	/**
	 *  Takes a position and evaluates it
	 *  Returns the evaluation based on who is moving, negate it if black
	 *  (need to be like that for the search)
	 *
	 *  @param Chessboard The position
	 *  @return int The evaluation of the position
	 */
	public static int evaluatePosition(Chessboard board)
	{

		// Check for draw by fifty moves rule
		if(board.movesFifty >= 50) return DRAW_VALUE * board.toMove; // Negate if we ever choose another value than0 for draw

		
	
		int evaluation = 0; // Initialize the evaluation
		int totalPiecesRemaining = 0;
		boolean backRow = false;
		boolean centerSquare = false;
		 
	    boolean drawn_by_material = true;
		   
	    boolean hasBishop = false;
		boolean hasKnight = false;
		int gamePhase = 0;
		
		if(board.movesFull <=10)
			board.gamePhase = OPENING;
		else if(board.movesFull <=22)
			board.gamePhase = MIDDLE_GAME;
		if(board.movesFull > 25)
			board.gamePhase = END_GAME;
		
		Piece piece;
		
		Iterator i = board.whitePieces.iterator();
		// we will loop through our two piece arrays to see if enough material is remaining
		while  (i.hasNext()){
			
			// reset for each piece
			backRow = false;
			centerSquare = false;
			
			piece = (Piece)i.next();
			
			if(piece.piece_location != OFF_BOARD){
				
				evaluation += PIECE_VALUE_ARRAY[piece.piece_type  + 7];
				totalPiecesRemaining++;
			
				// penalize b on bishops, knights, and queens on the back row by 1 point
				if(piece.piece_location >= 0 && piece.piece_location <= 7){
					backRow = true;				
				}
			
				if((piece.piece_location >= 34 && piece.piece_location <= 37) || (piece.piece_location >= 50 && piece.piece_location <= 53) ||
					(piece.piece_location >= 66 && piece.piece_location <= 69) || (piece.piece_location >= 82 && piece.piece_location <= 85)){
					centerSquare = true;
				}
								
				switch (piece.piece_type){
				
					case WHITE_KING:
					{
						if (piece.piece_location == 0 || piece.piece_location == 1 ||
								piece.piece_location == 6 || piece.piece_location == 7);
							evaluation += SAFE_KING_BONUS;
					}
					case WHITE_QUEEN: 
					{
						if (gamePhase == OPENING)
							if(backRow == true || (piece.piece_location >= 16 && piece.piece_location <= 23))
									evaluation = evaluation + (-BACK_ROW_PENALTY);
						break;
					}
						
					case WHITE_PAWN:
						if (piece.piece_location == 51 || piece.piece_location == 52 ||
							piece.piece_location == 67 || piece.piece_location == 68){
							evaluation = evaluation + 25;
						}
						break;
					case WHITE_ROOK:
					{
						// we return false at our soonest opportunity
						drawn_by_material = false;
						break;
					}
					case WHITE_BISHOP:
					{
						if (hasBishop == true || hasKnight == true)
							drawn_by_material = false;
						hasBishop = true;
						if (backRow == true)
							evaluation = evaluation + BACK_ROW_PENALTY;
						break;
					}
					case WHITE_KNIGHT:
					{
						if (hasBishop == true)
							drawn_by_material = false;
						hasKnight = true;
						if (centerSquare==true)
							evaluation = evaluation + CENTER_SQUARE_BONUS;
						if (backRow == true)
							evaluation = evaluation + BACK_ROW_PENALTY;
						break;
					}
				}
			}
		}
		
		// white has only draw material, check black
		//
		hasBishop = false;
		hasKnight = false;
		
		i = board.blackPieces.iterator();
		
		while  (i.hasNext()){
			
			// reset for each piece
			backRow=false;
			centerSquare=false;
			
			piece = (Piece)i.next();
			if(piece.piece_location != OFF_BOARD){
		
				evaluation += PIECE_VALUE_ARRAY[piece.piece_type  + 7];
				totalPiecesRemaining++;
			
				// penalize b on bishops, knights, and queens on the back row by 1 point
				if(piece.piece_location >= 112 && piece.piece_location <= 119){
					backRow = true;					
				}		
									
				switch (piece.piece_type){
				
					case BLACK_KING:
					{
						if (piece.piece_location == 112 || piece.piece_location == 113 ||
								piece.piece_location == 118 || piece.piece_location == 119);
							evaluation -= SAFE_KING_BONUS;
					}
					case BLACK_QUEEN:
					{
						if (gamePhase == OPENING)
							if(backRow == true || (piece.piece_location >= 16 && piece.piece_location <= 23))
									evaluation = evaluation - (-BACK_ROW_PENALTY);  // reverse back row penalty since we are using it as a bonus
						break;
					}
					case BLACK_PAWN: 
						if (piece.piece_location == 51 || piece.piece_location == 52 ||
								piece.piece_location == 67 || piece.piece_location == 68){
							evaluation = evaluation - 25;
						}
						break; 
					case BLACK_ROOK:
					{
						//we return false at our soonest opportunity
						drawn_by_material = false;
						break;
						
					}
					case BLACK_BISHOP:
					{
						if (hasBishop == true || hasKnight == true)
							drawn_by_material = false;
						hasBishop = true;
						if (backRow == true)
							evaluation = evaluation - BACK_ROW_PENALTY;
						break;
					}
					case BLACK_KNIGHT:
					{
						if (hasBishop == true)
							drawn_by_material = false;
						hasKnight = true;
						if (centerSquare==true)
							evaluation = evaluation - CENTER_SQUARE_BONUS;
						if (backRow == true)
							evaluation = evaluation - BACK_ROW_PENALTY;
						break;
					}
				}
			}
		}
		
		// add castling bonuses/punishments
		
		if (board.white_castle == CASTLE_NONE)
			evaluation = evaluation + CASTLING_PENALTY;
		if (board.black_castle == CASTLE_NONE)
			evaluation =  evaluation - CASTLING_PENALTY;
		
		if (board.whiteCastleStatus == true)
			evaluation = evaluation + CASTLING_BONUS;
		if (board.blackCastleStatus == true)
			evaluation =  evaluation - CASTLING_BONUS;
		
		if(drawn_by_material) return DRAW_VALUE * board.toMove;
				
		else{
			return evaluation * board.toMove; 
		}
	}
	// END evaluatePosition()
	
	/**
	 *  Takes a board and checks for mate/stalemate
	 *
	 *  @param Chessboard The board to check
	 *  @param int What ply the mate is found on
	 *  @return int The evaluation
	 */
	public static int mateCheck(Chessboard board, int ply)
	{
		LOG.debug("Entering mateCheck");
		// Start with finding the king
        Piece piece;
        int locationKing = -1;       
        Iterator i;
        
        if (board.toMove == WHITE_TO_MOVE)
        	i=board.whitePieces.iterator();
        else
        	i=board.blackPieces.iterator();
        
        // find the location of the king on the board	
		while(i.hasNext()){
			piece = (Piece)i.next();
			if(piece.piece_type*board.toMove == WHITE_KING){
				locationKing = piece.piece_location;
				break;
			}			
		}
        	
        board.toMove *= -1; // Switch side to move on the board to get the other side's pieces to be the attacker
		if(board.isAttacked(locationKing))
		{
			board.toMove *= -1; // Switch back side to move	
			
			return -(MATE_VALUE + ply); // Return the negative mate value + ply, negative since the 
		}
		else
		{
			board.toMove *= -1; // Switch back side to move
			return -DRAW_VALUE;
		}	



	}
	//END mateCheck()
	
	/**
	 *  CHecks to see if the position is a draw based on material
	 *  
	 *
	 *  @param Chessboard The position
	 *  @return int drawn true or false 
	 */
	public static boolean checkForDraw(Chessboard board){
		
		boolean hasBishop = false;
		boolean hasKnight = false;
		boolean isDrawn = false;
		Piece piece;
		
		Iterator i = board.whitePieces.iterator();
		// we will loop through our two piece arrays to see if enough material is remaining
		while  (i.hasNext()){
			
			piece = (Piece)i.next();
			
			
			switch (piece.piece_type){
			
				case WHITE_QUEEN: 
				case WHITE_PAWN: 
				case WHITE_ROOK:
				{
					// we return false at our soonest opportunity
					return false;				
				}
				case WHITE_BISHOP:
				{
					if (hasBishop == true || hasKnight == true)
						return false;
					hasBishop = true;
				}
				case WHITE_KNIGHT:
				{
					if (hasBishop == true)
						return false;
					hasKnight = true;
				}
			}
		}
		
		// white has only draw material, check black
		//
		hasBishop = false;
		hasKnight = false;
		i = board.blackPieces.iterator();
		
		while  (i.hasNext()){
			
			piece = (Piece)i.next();
					
			switch (piece.piece_type){
			
				case BLACK_QUEEN: 
				case BLACK_PAWN: 
				case BLACK_ROOK:
				{
					// we return false at our soonest opportunity
					return false;				
				}
				case BLACK_BISHOP:
				{
					if (hasBishop == true || hasKnight == true)
						return false;
					hasBishop = true;
				}
				case BLACK_KNIGHT:
				{
					if (hasBishop == true)
						return false;
					hasKnight = true;
				}
			}
		}
		
		// not enough material left -- return drawn = true
		return true;
				
	}
}
